package com.chuxing.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chuxing.config.ExamState;
import com.chuxing.domain.DTO.ExamListDTO;
import com.chuxing.domain.Exam;
import com.chuxing.domain.VO.PageResultVO;
import com.chuxing.exception.GenericException;
import com.chuxing.exception.UnauthorizedException;
import com.chuxing.system.mapper.ExamMapper;
import com.chuxing.system.service.AsyncService;
import com.chuxing.system.service.IExamService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 笔试信息Service业务层处理
 *
 * @author 刘祺
 * @date 2024-12-08
 */
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements IExamService {
    private final ExamMapper examMapper;

    private final AsyncService asyncService;

    /**
     * 查询笔试信息
     *
     * @param examId 笔试信息主键
     * @return 笔试信息
     */
    @Override
    public Exam selectExamByExamId(Long examId) {
        return examMapper.selectExamByExamId(examId);
    }

    /**
     * 查询笔试信息列表
     *
     * @param examListDTO 笔试信息
     * @return 笔试信息
     */
    @Override
    public PageResultVO selectExamList(ExamListDTO examListDTO) {
        // 验证分页参数
        int pageNum = Math.max(1, examListDTO.getPageNum());
        int pageSize = Math.max(1, examListDTO.getPageSize());
        // 启用分页查询
        PageHelper.startPage(pageNum, pageSize);
        try {
            try (Page<Exam> page = examMapper.selectExamList(examListDTO)) {
                // 构造并返回分页结果对象
                return new PageResultVO(page.getTotal(), page.getResult());
            }
        } catch (Exception e) {
            // 抛出通用异常，不暴露具体错误信息
            throw new GenericException("系统内部错误");
        } finally {
            // 确保分页查询结束
            PageHelper.clearPage();
        }
    }

    /**
     * 新增笔试信息
     *
     * @param exam 笔试信息
     * @return 结果
     */
    @Override
    public int insertExam(Exam exam) {
        Exam exam1 = examMapper.selectExamByStudentId(exam.getStudentId());
        if (exam1 != null) {
            throw new GenericException("该学生已提交过笔试信息");
        }
        // 验证笔试分数是否合法
        if (exam.getExamScore() != null && (exam.getExamScore() > 100 || exam.getExamScore() < 0)) {
            throw new GenericException("成绩不合法");
        }
        // 判断笔试分数是否通过
        if (exam.getExamScore() != null && exam.getExamScore() >= 60) {
            exam.setExamState(ExamState.EXAM_PASS);
        } else {
            exam.setExamState(ExamState.EXAM_FAIL);
        }
        // 如果笔试状态为空，则设置为未考试
        if (StrUtil.isBlank(exam.getExamState())) {
            exam.setExamState(ExamState.NO_EXAM);
        }
        // 设置创建时间为当前时间
        exam.setCreateTime(LocalDateTime.now());
        exam.setUpdateTime(LocalDateTime.now());
        return examMapper.insertExam(exam);
    }

    /**
     * 修改笔试信息
     *
     * @param exam 笔试信息
     * @return 结果
     */
    @Override
    public int updateExam(Exam exam) {
        // 验证笔试分数是否合法
        if (exam.getExamScore() != null && (exam.getExamScore() > 100 || exam.getExamScore() < 0)) {
            throw new GenericException("成绩不合法");
        }
        // 设置更新时间为当前时间
        exam.setUpdateTime(LocalDateTime.now());
        // 调用Mapper层更新笔试信息
        return examMapper.updateExam(exam);
    }

    /**
     * 批量删除笔试信息
     *
     * @param examIds 需要删除的笔试信息主键
     * @return 结果
     */
    @Override
    public int deleteExamByExamIds(Long[] examIds) {
        return examMapper.deleteExamByExamIds(examIds);
    }

    /**
     * 获取用户考试信息
     * 此方法用于获取当前登录用户的考试信息它首先检查用户是否已登录，
     * 如果未登录，则抛出UnauthorizedException异常如果用户已登录，
     * 则根据登录用户的ID查询并返回考试信息
     *
     * @return Exam 返回用户的考试信息对象
     * @throws UnauthorizedException 如果用户未登录，则抛出此异常
     */
    @Override
    public Exam getExamInfo(String studentId) {
        log.info("数据库查询考试信息{}", studentId);
        // 根据登录用户的ID查询考试信息
        return examMapper.selectExamByStudentId(studentId);
    }

    /**
     * 保存考试信息
     * 此方法首先检查用户是否已登录，以确保只有已登录的用户才能保存考试信息
     * 如果用户未登录，则抛出UnauthorizedException异常
     * 对于已登录的用户，方法会将考试信息与当前用户关联，并记录创建和更新时间
     * 为了确保数据的唯一性，方法会在插入新的考试记录之前，删除该学生已有的考试记录
     *
     * @param exam 要保存的考试信息对象，包含考试的相关数据
     * @throws UnauthorizedException 如果用户未登录，则抛出此异常
     */
    @Override
    public void saveExamInfo(Exam exam) {
        // 设置考试信息的创建时间为当前时间
        exam.setCreateTime(LocalDateTime.now());
        // 设置考试信息的更新时间为当前时间
        exam.setUpdateTime(LocalDateTime.now());
        //设置考试信息的状态为未考试
        exam.setExamState(ExamState.NO_EXAM);
        //设置考试分数为null
        exam.setExamScore(null);
        // 删除该学生已有的考试记录，以确保数据的唯一性
        examMapper.deletebyStudentId(exam.getStudentId());
        // 插入新的考试记录
        examMapper.insert(exam);
    }

    /**
     * 清除指定学生的考试记录
     * 此方法通过学生ID删除考试记录，并使用缓存清除功能
     * 以确保删除操作后，相关缓存数据保持一致
     *
     * @param studentId 学生ID，用于定位要清除考试记录的学生
     *                  注：使用了CacheEvict注解，当此方法执行时，它会清除名为"exam"的缓存中与指定学生ID相关的缓存项
     *                  这是为了避免缓存中保留了过时的数据，确保数据的一致性和准确性
     */
    @Override
    public void clear(String studentId) {
        // 删除该学生已有的考试记录
        examMapper.deletebyStudentId(studentId);
    }

    /**
     * 上传Excel文件
     * 此方法用于上传Excel文件，并解析其中的数据，将解析结果保存到数据库中
     *
     * @param file 要上传的Excel文件对象
     */
    @Override
    public void upload(MultipartFile file) {
        try {
            Sheet sheet = null;
            // 创建workbook对象，读取整个文档
            String originalFilename = file.getOriginalFilename();
            if (originalFilename != null) {
                if (originalFilename.toLowerCase().endsWith(".xlsx")) {
                    // xlsx格式
                    try (XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream())) {
                        sheet = workbook.getSheetAt(0);
                    }
                } else if (originalFilename.toLowerCase().endsWith(".xls")) {
                    // xls格式
                    try (HSSFWorkbook workbook = new HSSFWorkbook(file.getInputStream())) {
                        sheet = workbook.getSheetAt(0);
                    }
                } else {
                    throw new GenericException("请上传正确的文件格式");
                }
            } else {
                throw new GenericException("文件名为空，请上传有效的文件");
            }
            // 获取sheet的最大row下标
            int lastRowNum = sheet.getLastRowNum();
            int studentID = -1;
            int score = -1;
            // 检查Excel文件是否为空
            if (lastRowNum < 1) {
                throw new GenericException("请检查Excel文件是否为空");
            }
            // 遍历第一行，查找“学号”和“成绩”列的索引
            for (Cell cell : sheet.getRow(0)) {
                if (cell.getStringCellValue().equals("学号")) {
                    studentID = cell.getColumnIndex();
                }
                if (cell.getStringCellValue().equals("成绩")) {
                    score = cell.getColumnIndex();
                }
            }
            // 检查Excel文件格式是否包含学号和成绩
            if (studentID == -1 || score == -1) {
                throw new GenericException("请检查Excel文件格式是否包含学号和成绩");
            }
            List<Exam> examList = new ArrayList<>();
            // 遍历除标题行外的其他行，创建Exam对象并添加到列表中
            for (int i = 1; i <= lastRowNum; i++) {
                Row row = sheet.getRow(i);
                if (row == null) {
                    continue; // 跳过空行
                }
                Cell studentIdCell = row.getCell(studentID);
                Cell scoreCell = row.getCell(score);
                // 跳过空行或无效单元格
                if (studentIdCell == null || scoreCell == null || StrUtil.isBlank(getCellValueAsString(studentIdCell)) || StrUtil.isBlank(getCellValueAsString(scoreCell))) {
                    continue;
                }
                Exam exam = new Exam();
                exam.setStudentId(getCellValueAsString(studentIdCell));
                exam.setExamScore((long) getCellValueAsNumeric(scoreCell));
                if (exam.getExamScore() > 100 || exam.getExamScore() < 0) {
                    throw new GenericException("请检查Excel文件成绩是否符合要求");
                }
                examList.add(exam);
            }
            // 异步保存数据
            log.info("开始异步保存数据");
            asyncService.uploadAsync(examList);
        } catch (GenericException e) {
            throw new GenericException(e.getMessage());
        } catch (IOException e) {
            throw new GenericException("文件上传失败，请检查文件格式");
        }

    }

    /**
     * 重写export方法以导出笔试信息
     *
     * @param response    HTTP响应对象，用于输出Excel格式的笔试信息
     * @param examListDTO 包含笔试信息查询条件的数据传输对象
     */
    @Override
    public void export(HttpServletResponse response, ExamListDTO examListDTO) {
        try {
            // 获取笔试信息列表
            PageResultVO list = selectExamList(examListDTO);
            // 获取笔试信息记录列表
            List<Exam> records = list.getRecords();
            // 加载笔试信息模板文件
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("static/笔试信息.xlsx");
            // 创建Workbook对象用于操作Excel文件
            Workbook workbook = null;
            // 检查是否成功加载模板文件
            if (resourceAsStream != null) {
                workbook = new XSSFWorkbook(resourceAsStream);
            }
            if (workbook != null) {
                // 获取第一个Sheet页
                Sheet sheet = workbook.getSheetAt(0); //读取
                for (int i = 0; i < records.size(); i++) {
                    // 创建新行
                    Row row = sheet.createRow(i + 1);
                    int j = 0;
                    // 写入笔试信息记录
                    row.createCell(j++).setCellValue(records.get(i).getStudentId());
                    if (records.get(i).getExamScore() != null) {
                        row.createCell(j++).setCellValue(records.get(i).getExamScore());
                    }
                }
                // 将Excel数据写入HTTP响应流
                workbook.write(response.getOutputStream());
                // 关闭Workbook对象
                workbook.close();
                // 关闭输入流
                resourceAsStream.close();
                // 刷新HTTP响应流
                response.getOutputStream().flush();
                // 关闭HTTP响应流
                response.getOutputStream().close();
                log.info("管理员导出笔试信息列表成功");
            }
        } catch (IOException e) {
            throw new GenericException("导出笔试信息列表失败");
        }
    }

    @Override
    public void deleteAll() {
        examMapper.deleteAll();
    }

    @Override
    public void checkExamScoreLine(Integer score) {
        if (score == null){
            throw new GenericException("请输入分数线");
        }
        if(score<0||score>100){
            throw new GenericException("请输入正确的分数线");
        }
        //查询所有考试信息
        List<Exam> examList = examMapper.selectList(new QueryWrapper<>());
        //如果考试信息大于等于score,修改考试信息为已通过否则设为未通过
        for (Exam exam : examList) {
            if (exam.getExamScore() >= score) {
                exam.setExamState(ExamState.EXAM_PASS);
            } else {
                exam.setExamState(ExamState.EXAM_FAIL);
            }
        }
        //清空数据库
        examMapper.deleteAll();
        //插入数据库
        if(!examList.isEmpty()){
            examMapper.insert(examList);
        }
    }


    /**
     * 将Excel单元格的值转换为字符串
     * 此方法处理各种类型的单元格，并将其内容转换为字符串格式
     * 对于空单元格或不支持的单元格类型，返回空字符串
     *
     * @param cell Excel单元格对象，可能为null
     * @return 单元格内容的字符串表示，如果单元格为null或类型不支持，则返回空字符串
     */
    private String getCellValueAsString(Cell cell) {
        // 处理空单元格
        if (cell == null) {
            return "";
        }
        // 处理单元格类型
        return switch (cell.getCellType()) {
            case STRING -> cell.getStringCellValue();
            case NUMERIC -> String.valueOf(cell.getNumericCellValue());
            case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
            case FORMULA -> cell.getCellFormula();
            default -> "";
        };
    }

    /**
     * 将单元格的值转换为数值类型
     * 此方法用于处理Excel表格中的单元格，将其内容转换为整数数值
     * 如果单元格为空、包含非数值字符串或不是NUMERIC类型，方法将返回0
     *
     * @param cell 单元格对象，表示Excel表格中的一个单元格
     * @return int 转换后的整数值，如果转换失败或单元格为空，则返回0
     */
    private int getCellValueAsNumeric(Cell cell) {
        // 检查单元格是否为空，为空则返回0
        if (cell == null) {
            throw new GenericException("请检查Excel文件成绩是否符合要求");
        }
        // 根据单元格的类型，选择合适的处理方式
        switch (cell.getCellType()) {
            case NUMERIC:
                // 如果单元格类型为NUMERIC，则直接将其内容转换为int类型并返回
                return (int) cell.getNumericCellValue();
            case STRING:
                // 如果单元格类型为STRING，则尝试将其内容转换为int类型
                try {
                    // 尝试将字符串转换为整数，如果转换成功则返回转换后的数值
                    return Integer.parseInt(cell.getStringCellValue());
                } catch (NumberFormatException e) {
                    throw new GenericException("请检查Excel文件成绩是否符合要求");
                }
                // 对于其他单元格类型，统一返回0
            default:
                throw new GenericException("请检查Excel文件成绩是否符合要求");
        }
    }
}
